import logging
import ujson as json
from celery_app import celery, DBSession, Inspect, AsyncResult
import time, random, datetime
from pydantic import BaseModel
from sqlalchemy import text
import traceback
from enums.work_order_status_enum import WorkOrderStatusEnum
from utils.commit.submit_wjx_v2 import convert_data_to_submit_data, do_submit
from utils.time_utils import convert_shanghai_to_utc_time
from model.model import WorkOrderExtend, Topic, WorkOrder


# 定义任务执行函数
@celery.task()
def my_task(task_id):
    # 执行任务的逻辑
    time.sleep(random.uniform(0, 2))
    print(123)
    print(f"Task {task_id} executed at {datetime.datetime.now()}")


class SendWjxResultView(BaseModel):
    work_order_id: int  # 工单id
    db_ids: list  # 需要执行的一批数据库id
    item_answer_start: int  # 单个问卷提交的开始时间
    item_answer_end: int  # 单个问卷提交的结束时间
    title_link: str  # 问卷链接
    area_value_weight_dict: dict  # 区域的提交权重

    @staticmethod
    def init_obj(wo_id, db_ids, ias, iae, link, av_wd):
        return SendWjxResultView(work_order_id=wo_id, db_ids=db_ids, item_answer_start=ias, item_answer_end=iae,
                                 title_link=link, area_value_weight_dict=av_wd)


@celery.task()
def child_task(o: str):
    print(f'这里是调用的子定时任务,子任务的id是：{o}')


def get_topic_updated_obj(item_topic: Topic, status: int, submit_start_time, submit_end_time, request_res, request_ip):
    """
    更新当前数据提交后的结果状态
    :param item_topic:
    :param status:
    :param submit_start_time: 提交开始时间
    :param submit_end_time: 提交结束时间
    :param request_res: 请求结果
    :param request_ip: 请求的ip地址
    :return:
    """
    item_topic.submitted = status
    item_topic.submit_start_time = submit_start_time
    item_topic.submit_end_time = submit_end_time
    item_topic.request_res = request_res
    item_topic.request_ip = request_ip
    return item_topic


def task_stop(work_order_id):
    """
    通过任务id停止任务进行,
    :param work_order_id:
    :return:
    """
    # 1。工单默认存在
    # 2。查询还未开始运行的定时任务
    # 使用inspect模块的registered函数获取所有注册的任务
    registered_tasks = Inspect().registered()

    # 提取任务ID
    task_ids = []
    for worker, tasks in registered_tasks.items():
        for task_name in tasks:
            task_ids.append(task_name)
    # # 输出所有任务ID
    # for task_id in task_ids:
    #     print(task_id)
    prefix = str(work_order_id) + '_'
    filtered_strings = list(filter(lambda s: s.startswith(prefix), task_ids))
    # 3。删除未运行的定时任务
    for task_id in filtered_strings:
        result = AsyncResult(task_id)
        result.revoke(terminate=True)
    session = DBSession()
    item = session.query(WorkOrder).filter_by(id=work_order_id).first()
    if item:
        item.status = WorkOrderStatusEnum.WAITED_SEND.value
    session.commit()
    return filtered_strings


@celery.task()
def send_item_db_result(obj_dict: dict):
    print(f'这里是send_item_db_result定时任务的对象：{obj_dict}')
    # 发送每一条数据库的数据
    obj = SendWjxItemView(**obj_dict)
    work_order_id = obj.work_order_id
    db_id = obj.db_id
    send_times = obj.send_times
    db_title_arr = obj.db_title_arr
    send_area = obj.send_area
    link = obj.link
    sleep_time = obj.sleep_time
    # 在Celery应用程序上注册数据库会话
    session = DBSession()
    # session.add()
    # session.close()
    cur_topic = session.query(Topic).filter_by(id=db_id).first()
    print(f'这里是send_item_db_result定时任务的对象：select_topic')
    if not cur_topic:
        return
    if cur_topic.submitted == 2:
        return  # 这份数据已发送完成，直接返回
    wo_topic_id = '%s_%s' % (str(work_order_id), str(db_id))  # 工单和这条数据的id
    logging.debug('send_item提交数据，data_id:{wo_topic_id}')
    send_data = cur_topic.data
    print(f'这里是send_item_db_result定时任务的对象：xxxxxxx2')
    if send_times >= 5:  # 自动重试默认发送5次,5次之后直接失败
        # 修改这条数据为发送失败
        oo = get_topic_updated_obj(cur_topic, -1, cur_topic.submit_start_time, int(time.time()), cur_topic.request_res,
                                   cur_topic.request_ip)
        # 修改工单的状态为发送失败
        item = session.query(WorkOrder).filter_by(id=work_order_id).first()
        if item:
            item.status = WorkOrderStatusEnum.SEND_ERROR.value
            session.add(item)
        session.add(oo)
        session.commit()
        return
    else:
        t_start = int(time.time())
        request_str = ''
        ip_res = ''
        try:
            # 1. 组装问卷发送数据
            submit_data = convert_data_to_submit_data(db_title_arr, json.loads(send_data))
            # 2. 发送问卷
            # request_res, ip_res = do_submit(send_area, link, submit_data, sleep_time, wo_topic_id)
            # request_str = request_res if len(request_res) < 510 else request_res[0:510]
            print('调用发送问卷方法')
        except Exception as e:
            traceback.print_exc()
            request_str = str(e)[0:510] if e else '请求异常'
        finally:
            t_end = int(time.time())
            if request_str.startswith('10〒'):  # 以10开头表示请求成功, 其余的表示请求失败
                # 3. 修改这一批次的数据状态为2（发送完成）
                oo = get_topic_updated_obj(cur_topic, 2, t_start, t_end, request_str, ip_res)
                session.add(oo)
                session.commit()
                # 查询工单是否全部发送完成-发送完成则置为发送成功
                # b_res, status_obj = search_send_status(work_order_id)
                # if b_res and status_obj.get('total') == status_obj.get('finished'):
                #     item = session.query(WorkOrder).filter_by(id=work_order_id).first()
                #     if item:
                #         item.status = WorkOrderStatusEnum.FINISHED.value
                #         session.add(item)
                #         session.commit()
            elif request_str.startswith('9〒'):  # 以9开头表示答案错误
                oo = get_topic_updated_obj(cur_topic, -1, t_start, t_end, request_str, ip_res)
                session.add(oo)
                session.commit()
            elif request_str.startswith('18〒'):  # 以18开头表示问卷题目已被修改，停止所有后续问卷的发送
                task_stop(work_order_id=work_order_id)
                send_times += 10  # 整个工单停止发送
            else:
                oo = get_topic_updated_obj(cur_topic, cur_topic.submitted, t_start, t_end, request_str, ip_res)
                session.add(oo)
                session.commit()
                logging.debug('发送失败，自动重试；重试第' + str(send_times) + '次')
                # 运行时间
                resend_time_unix = datetime.datetime.now() + datetime.timedelta(milliseconds=1000)
                # resend+根据时间戳(ms)作为唯一表示
                resend_task_id = 'resend' + wo_topic_id + str(int(time.time() * 1000))
                # 若发送失败，则重新发送
                send_times += 1
                utc_deal_time = convert_shanghai_to_utc_time(resend_time_unix)  # 将本地时区转换为celery需要执行的utc时区
                params = SendWjxItemView.init_obj(db_id=db_id, sleep_time=sleep_time, db_title_arr=db_title_arr,
                                                  link=link, work_order_id=work_order_id, send_area=send_area,
                                                  send_times=send_times)
                send_item_db_result.apply_async(args=[params.__dict__], eta=utc_deal_time, task_id=resend_task_id)
    return


@celery.task()
def send_wjx_result(obj_dict: dict):
    # 发送一批数据库的数据
    print(f'这里是定时任务的对象：{obj_dict}')
    # 1。查询这一批次需要的数据
    obj = SendWjxResultView(**obj_dict)
    work_order_id = obj.work_order_id
    link = obj.title_link
    ids = obj.db_ids
    area_value_weight_dict = obj.area_value_weight_dict
    item_answer_start = obj.item_answer_start
    item_answer_end = obj.item_answer_end
    if len(ids) <= 0:  # 数据不存在，无法进行操作
        return
    # 查询提交数据与生成数据的映射关系
    session = DBSession()
    try:
        wo_extend = session.query(WorkOrderExtend).filter_by(work_order_id=work_order_id).first()
        if not wo_extend:
            return
        db_title_arr = wo_extend.title_maps
        # 随机值
        keys = list(area_value_weight_dict.keys())
        weights = list(area_value_weight_dict.values())
        # 2。提交这一批次的数据
        for k in ids:
            choice_arr = random.choices(keys, weights=weights, k=len(ids))
            if choice_arr:
                random_area = choice_arr[0]
            else:  # 如果生成失败则为全国
                random_area = 'all'
            task_id = str(work_order_id) + '_child_' + str(k)  # 根据时间戳(ms)作为唯一表示
            a = datetime.datetime.now() + datetime.timedelta(milliseconds=100)
            # 内部任务立即执行
            random_int = random.randint(item_answer_start, item_answer_end)  # 每行数据生成一个随机数，代表这行数据需要这么多秒后再发送
            utc_deal_time = convert_shanghai_to_utc_time(a)  # 将本地时区转换为celery需要执行的utc时区
            params = SendWjxItemView.init_obj(db_id=k, sleep_time=random_int, db_title_arr=db_title_arr, link=link,
                                              work_order_id=work_order_id, send_area=random_area, send_times=0)
            send_item_db_result.apply_async(args=[params.__dict__], eta=utc_deal_time, task_id=task_id)
        # 3。修改这一批次的数据状态为1（发送中）
        # batch_update_status(data_items, 1)
        session.query(Topic).filter(Topic.id.in_(ids)).update({Topic.submitted: 1}, synchronize_session=False)
        session.commit()
    except Exception as e:
        print('创建任务出错')
    finally:
        session.close()
        return


class SendWjxItemView(object):
    """
    :param db_id: 这条数据的数据库id
    :param sleep_time: 需要暂停的时间
    :param db_title_arr: 问卷所有标题对应的数据库中的标题内容
    :param link: 问卷的链接
    :param work_order_id: 工单id
    :param send_area: 标准区域的编码，全国是all
    :param send_times: 发送次数 大于等于5，则发送失败
    """
    db_id: int
    sleep_time: int
    db_title_arr: []
    link: str
    work_order_id: int
    send_area: str
    send_times: int

    def __init__(self, db_id, sleep_time, db_title_arr, link, work_order_id, send_area, send_times):
        self.db_id = db_id
        self.sleep_time = sleep_time
        self.db_title_arr = db_title_arr
        self.link = link
        self.work_order_id = work_order_id
        self.send_area = send_area
        self.send_times = send_times

    @staticmethod
    def init_obj(db_id, sleep_time, db_title_arr, link, work_order_id, send_area, send_times):
        return SendWjxItemView(db_id=db_id, sleep_time=sleep_time, db_title_arr=db_title_arr, link=link,
                               work_order_id=work_order_id, send_area=send_area, send_times=send_times)
